using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Windows.Forms;
////////////////////////////////////////////////////////////////////////
// RDataNode
////////////////////////////////////////////////////////////////////////

/**
* RDataNode implements data (leaf) nodes in the R*-tree

* the block of the RTDirNode is organised as follows:
* +--------+---------+---------+-----+------------------+
* | header | Data[0] | Data[1] | ... | Data[capacity-1] |
* +--------+---------+---------+-----+------------------+

* the header of the RTDirNode is organised as follows:
* +-------+-------------+
* | level | noEntries |
* +-------+-------------+
*/
namespace RStarTree
{
    public  class RTDataNode : RTNode, Node
    {
        Data[] data;
        public RTDataNode(RTree rt):base(rt)
        // create a brand new RTDataNode
        {
            

            byte[] b;
            int header_size;
            Data d;

            level = 0;

            // times briefly create a data entry and look how big that is ..
            d = new Data(dimension);

            header_size = Constants.SIZEOF_SHORT  //level
                          + Constants.SIZEOF_INT; //noEntries
            capacity = (rt.file.getBlocklength() - header_size) / d.getSize();
           // Console.WriteLine("RTDataNode created. Id: " + rt.noOfData);
           // Console.WriteLine("RTDataNode capacity " + capacity);

            //RTDataNode__dimension = dimension;
            data = new Data[capacity];

            // create a new block for the node
            b = new byte[rt.file.getBlocklength()];
            try
            {
                block = rt.file.appendBlock(b);
            }
            catch (Exception e)
            {
                Utilities.processError("RTDatanode creation: error in block appending", true);
            }

            rt.noOfData++;

            // Must be written to disk --> Set dirty bit
            dirty = true;
        }

        // this constructor reads an existing RTDataNode from the disk
        public RTDataNode(RTree rt, int _block) :base(rt)
        {
            byte[] b;
            int header_size;
            Data d;

            d = new Data(dimension);

            header_size = Constants.SIZEOF_SHORT //level
                        + Constants.SIZEOF_INT;  //noEntries
            capacity = (rt.file.getBlocklength() - header_size) / d.getSize();

            //RTDataNode__dimension = dimension;
            data = new Data[capacity];
            
            block = _block;
            try
            {
                b = new byte[rt.file.getBlocklength()];
                rt.file.readBlock(b, block);
                readFromBuffer(b);
            }
            catch (Exception e)
            {
                Utilities.processError("RTDataNode initialization: error in block reading", true);
            }


            dirty = false;
        }
        public void readFromBuffer(byte[] buffer)
        {
            MemoryStream byteIn = new MemoryStream(buffer);
            BinaryReader inReader = new BinaryReader(byteIn);

            // read header
            level = inReader.ReadInt16();
            level = 0; // level should be 0 anyway
            noEntries = inReader.ReadInt32();

            //Console.WriteLine("RTDataNode.read_from_buffer(): noEntries =" +noEntries);

            // read data
            for (int i = 0; i < noEntries; i++)
            {
                data[i] = new Data(dimension);
                data[i].readFromBuffer(inReader);
            }

            inReader.Close();
            byteIn.Close();
        }
        // serialize the object's information to the buffer[]
        public void writeToBuffer(byte[] buffer)
        {
            MemoryStream byteOut = new MemoryStream(buffer.Length);
            BinaryWriter outWriter = new BinaryWriter(byteOut);

            // write header
            level = 0;  // level should be 0 anyway
            outWriter.Write(level);
            outWriter.Write(noEntries);

            // write data
            for (int i = 0; i < noEntries; i++)
                data[i].writeToBuffer(outWriter);

            byte[] bytes = byteOut.ToArray();
            for (int i = 0; i < bytes.Length; ++i)
                buffer[i] = bytes[i];

            outWriter.Close();
            byteOut.Close();
        }
        public void print()
        {
            int i, n;

            n = getNoEntries();
            for (i = 0; i < n; i++)
            {
                Console.WriteLine(data[i].data[0] + " " + data[i].data[1]);
            }
            Console.WriteLine("level " + level);
        }
        public int getNoOfData()
        {
            return getNoEntries();
        }
        public float[] getMBR() // float calculates the mbr of all data in this node
        {
            int i, j, n;
            float[] mbr, tm;

            mbr = data[0].getMBR();
            n = getNoEntries();
            for (j = 1; j < n; j++)
            {
                tm = data[j].getMBR();
                for (i = 0; i < 2 * dimension; i += 2)
                {
                    mbr[i] = Math.Min(mbr[i], tm[i]);
                    mbr[i + 1] = Math.Max(mbr[i + 1], tm[i + 1]);
                }
            }
            return mbr;
        }
        /*
    * split this to this and the new node brother
    * called when the node overflows and a split has to take place.
    * This function splits the data into two partitions and assigns the
    * first partition as data of this node, and the second as data of
    * the new split node.
    * invokes RTNode.split() to calculate the split distribution
    */
        public void split(RTDataNode splitnode)
        // 	splits the current node so that's mbr m after splitnode be moved
        {
            int i, dist, n;
            int[][] distribution;
            float[][] mbr_array; // to be passed as parameter to RTNode.split()
            Data[] new_data1, new_data2;

            // initialize n, distribution[0]
            n = getNoEntries();
            distribution = new int[1][];

            // allocate mbr_array to contain the mbrs of all entries
            mbr_array = new float[n][];
            for ( i = 0; i < n; i++)
                mbr_array[i] = new float[2 * dimension];
            for (i = 0; i < n; i++)
            {
                mbr_array[i] = data[i].getMBR();
            }

            // calculate distribution[0], dist by calling RTNode.split()
            dist = base.split(mbr_array, distribution);

            // create new Data arrays to store the split results
            new_data1 = new Data[capacity];
            new_data2 = new Data[capacity];

            for (i = 0; i < dist; i++)
                new_data1[i] = data[distribution[0][i]];

            for (i = dist; i < n; i++)
                new_data2[i - dist] = data[distribution[0][i]];

            // set the new arrays as data arrays of this and splitnode's data
            data = new_data1;
            splitnode.data = new_data2;


            noEntries = dist;
            splitnode.noEntries = n - dist;  // must remain so because of rounding! !!
        }
        /*
   * insert a new data entry in this node
   * this function may cause some data to be reinserted and/or the node to split
   * NOTE: the parameter sn is a reference call to sn[0] which will be
   * a new node after a potential split and NOT an array of nodes
   */
        public int insert(Data d, RTNode[] sn)
        // liefert false, wenn die Seite gesplittet werden muss
        {
            int i, last_cand;
            float[] mbr, center;
            SortMbr[] sm; //used for REINSERT
            Data nd;
            Data[] new_data;

            if (getNoEntries() == capacity)
                Utilities.processError("RTDataNode.insert: maximum capacity violation", true);

            // insert data into the node
            data[getNoEntries()] = d;
            noEntries++;

            // Plate block for writing mark
            dirty = true;

            if (getNoEntries() == (capacity - 1))
            // overflow
            {
                if (myTree.re_level[0] == false)
                // there was no reinsert on level 0 during this insertion
                // --> reinsert 30% of the data
                {
                    // calculate center of page
                    mbr = getMBR();
                    center = new float[dimension];
                    for (i = 0; i < dimension; i++)
                        center[i] = (mbr[2 * i] + mbr[2 * i + 1]) / (float)2.0;



                    //construct a new array to hold the data sorted according to their distance
                    //from the node mbr's center
                    new_data = new Data[capacity];

                    // initialize array that will sort the mbrs
                    sm = new SortMbr[noEntries];
                    for (i = 0; i < noEntries; i++)
                    {
                        sm[i] = new SortMbr();
                        sm[i].index = i;
                        sm[i].dimension = dimension;
                        sm[i].mbr = data[i].getMBR();
                        sm[i].center = center;
                    }

                    // sort by distance of each center to the overall center
                    Utilities.quickSort(sm, 0, sm.Length - 1, Constants.SORT_CENTER_MBR);

                    last_cand = (int)((float)noEntries * 0.30);

                    // copy the nearest 70% candidates to new array
                    for (i = 0; i < noEntries - last_cand; i++)
                        new_data[i] = data[sm[i].index];

                    // insert last 30% candidates into reinsertion list
                    for (; i < noEntries; i++)
                    {
                        nd = new Data(dimension);
                        nd = data[sm[i].index];
                        myTree.re_data_cands.insert(nd);
                    }

                    data = new_data;

                    myTree.re_level[0] = true;

                    // correct # of entries
                    noEntries -= last_cand;

                    // must write page
                    dirty = true;

                    return Constants.REINSERT;
                }
                else
                {
                    // reinsert was applied before
                    // --> split the node
                    sn[0] = new RTDataNode(myTree);
                    sn[0].level = level;
                    split((RTDataNode)sn[0]);
                }
                return Constants.SPLIT;
            }
            else
                return Constants.NONE;
        }
        public Data get(int i)
        {
            Data d;

            if (i >= getNoEntries())
                // if there is no i-th object -. null
                return null;

            d = new Data(dimension);
            d = data[i];

            return d;
        }
        public void region(float[] mbr)
        {
            int i, n, j;
            float[] dmbr;

            n = getNoEntries();
            for (i = 0; i < n; i++)
            // check all overlapping rectangles on
            {
                dmbr = data[i].getMBR();
                if (Utilities.isRectInterSection(dimension, dmbr, mbr))
                {
                    Console.WriteLine("( ");
                    for (j = 0; j < dimension; j++)
                        Console.WriteLine(" " + dmbr[j]);

                    Console.WriteLine(" )");
                }
            }
        }
        public void pointQuery(float[] p)
        {
            int i, n, j;
            float[] dmbr;

            //#ifdef ZAEHLER
            //    page_access++;
            //#endif

            n = getNoEntries();
            for (i = 0; i < n; i++)
            // check all overlapping rectangles on
            {
                dmbr = data[i].getMBR();
                if (Utilities.isRectInterSection(dimension, p, dmbr))
                {
                    //        Console.WriteLine("( ");
                    //        for( j = 0; j < dimension; j++)
                    //            Console.WriteLine(" %f",dmbr[j]);
                    //
                    //        Console.WriteLine(" ) \n");
                }

            }
        }
        public void pointQuery(PPoint p, SortedLinList res)
        {
            int i, n, j;
            float[] dmbr;


            myTree.page_access++;

            n = getNoEntries();
            for (i = 0; i < n; i++)
            // check all overlapping rectangles on
            {
                dmbr = data[i].getMBR();
                if (Utilities.isInside(p.data, dmbr, dimension))
                {
                    res.insert(data[i]);
                }
            }
        }
        public void rangeQuery(float[] mbr, SortedLinList res)
        {
            int i, n, j;
            float[] dmbr;
            Data rect;
            PPoint center;

            //#ifdef ZAEHLER
            //    page_access += myTree.node_weight[0];
            //#endif

            myTree.page_access++;

            n = getNoEntries();
            for (i = 0; i < n; i++)
            {
                dmbr = data[i].getMBR();
                if (Utilities.isRectInterSection(dimension, dmbr, mbr))
                {
                    // Center des MBRs ausrechnen
                    //center = new Data(dimension);
                    //for(i = 0; i < dimension; i++)
                    //    center.data[i] = (mbr[2*i] + mbr[2*i+1]) / 2;
                    //point.distance = Constants.objectDIST(point,center);
                    res.insert(data[i]);
                }
            }
        }
        public Boolean isDataNode()        // this is a data node
        {
            return true;
        }
        public void ringQuery(PPoint center, float radius1, float radius2, SortedLinList res)
        {
            int i, n, j, k;
            float[] dmbr;

            //#ifdef ZAEHLER
            //    page_access += myTree.node_weight[0];
            //#endif

            myTree.page_access++;

            n = getNoEntries();
            for (i = 0; i < n; i++)
            {
                dmbr = data[i].getMBR();
                if (Utilities.isRingInterSection(dimension, dmbr, center, radius1, radius2))
                {
                    Console.WriteLine("RTDataNode: section ring succeeded");
                    //PPoint point;
                    //point = new PPoint(dimension);
                    //for( j = 0; j < dimension; j++)
                    //    point.data[j] = dmbr[2*j];
                    //point.distance = Constants.objectDIST(point,center);
                    res.insert(data[i]);
                }
            }
        }
        public void rangeQuery(PPoint center, float radius, SortedLinList res)
        {
            int i, n, j, k;
            float[] dmbr;


            /////////////////////////////////////////////////////
            myTree.page_access++;
            myTree.data_access++;

            //byte[] block = new byte[myTree.file.getBlocklength()];
            //myTree.file.readBlock(block, 0);
            //////////////////////////////////////////////////////

            n = getNoEntries();
          //  Console.WriteLine(" R TREE : n:" + n.ToString());
          //  Console.WriteLine(" R TREE : candidates:" + res.getNoOfElements().ToString());

            for (i = 0; i < n; i++)
            {
                dmbr = data[i].getMBR();
                if (Utilities.isCircleInterSection(dimension, dmbr, center, radius))
                {
                    //Console.WriteLine("RTDataNode: section succeeded");
                    //PPoint point;
                    //point = new PPoint(dimension);
                    //for( j = 0; j < dimension; j++)
                    //    point.data[j] = dmbr[2*j];
                    //point.distance = Constants.objectDIST(point,center);
                    res.insert(data[i]);
                }
            }
        }
        public void nearestNeighborSearch(PPoint QueryPoint, PPoint Nearest, float nearest_distance)
        {
            int i, j;
            float nearest_dist, distance;

            for (i = 0; i < getNoEntries(); i++)
            {

                distance = Utilities.MINDIST(QueryPoint, get(i).getMBR());

                if (distance <= nearest_distance)
                {
                    nearest_distance = distance;
                    Nearest.distance = distance;
                    for (j = 0; j < 2 * dimension; j++)
                        Nearest.data[j] = get(i).data[j];
                }
            }
        }
        public void nearestNeighborSearch(PPoint QueryPoint, SortedLinList res, float nearest_distance)
        {
            int i, j, k;
            float nearest_dist, distance;
            Boolean t;
            Data neu, dummy, element;

            k = res.getNoOfElements();

            for (i = 0; i < getNoEntries(); i++)
            {
                element = get(i);

                distance = Utilities.MINDIST(QueryPoint, element.getMBR());


                if (distance <= nearest_distance)
                {

                    dummy = (Data)res.get(k - 1);
                    t = res.erase();


                    element.distance = distance;
                    res.insert(element);

                    nearest_distance = ((Data)res.get(k - 1)).distance;
                }
            }
        }
        public void range_nnQuery(float[] mbr, SortedLinList res, PPoint center, float nearest_distance, PPoint Nearest, Boolean success)
        {
            int i, j, n;
            float nearest_dist, distance;
            float[] dmbr;
            PPoint point;

            if (success)
            {
                rangeQuery(mbr, res);
                return;
            }
            n = getNoEntries();
            for (i = 0; i < n; i++)
            {
                dmbr = data[i].getMBR();
                if (Utilities.isRectInterSection(dimension, dmbr, mbr))
                {
                    point = new PPoint(dimension);
                    for (j = 0; j < dimension; j++)
                        point.data[j] = dmbr[2 * j];
                    point.distance = Utilities.objectDIST(point, center);
                    res.insert(point);
                    success = true;
                }
            }

            if (!success)
            {
                for (i = 0; i < getNoEntries(); i++)
                {
                    //distance = Constants.objectDIST(center,get(i));
                    distance = Utilities.MINDIST(center, get(i).getMBR());

                    if (distance <= nearest_distance)
                    {
                        nearest_distance = distance;
                        for (j = 0; j < dimension; j++)
                            Nearest.data[j] = get(i).data[j];
                    }
                }
            }
        }
        public void overlapping(float[] p, int[] nodes_t)
        { 
            nodes_t[0]++; 
        }
        public void nodes(int[] nodes_a)     // see RTree.nodes()
        { 
            nodes_a[0]++; 
        }
        public void delete()
        {
            if (dirty)
            {
                byte[] b = new byte[myTree.file.getBlocklength()];
                try
                {
                    writeToBuffer(b);
                    myTree.file.writeBlock(b, block);
                }
                catch (IOException e)
                {
                    Utilities.processError("RTDirNode delete: Error in writing block", true);
                }
            }
            
        }

    }
}
